/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */
#include "base-topology.h"
#include "tokenizer.h"
#include "ns3/point-to-point-helper.h"
#include "ns3/net-device-container.h"
#include "ns3/log.h"
#include "ns3/node.h"
#include "ns3/channel.h"
#include "ns3/queue.h"
#include "ns3/pointer.h"
#include "ns3/nstime.h"
#include "ns3/uinteger.h"
#include "ns3/test.h"
#include "ns3/enum.h"
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

NS_LOG_COMPONENT_DEFINE ("BaseTopology");

namespace ns3 {
namespace eecityu {

using namespace std;
using namespace ns3;

NS_OBJECT_ENSURE_REGISTERED (BaseTopology);


//////////////////////////////////
// Version 1 API implementation //
//////////////////////////////////

/**
 * \brief Print all link/arc attributes on a line in the BRITE-NS topology file
 * \param v The vector the string after a line of link/arc definition is tokenized
 * \return A string for being printed by NS_LOG_*
 */
string 
PrintStringVector (const vector<string>& v)
{
  ostringstream debugOutput;
  debugOutput << "Tokenized Link/arc definition: ";
  for (vector<string>::size_type i=0; i<v.size (); i++)
  {
    debugOutput << "<" << v.at (i) << ">";
  }
  debugOutput << ends;
  return debugOutput.str ();
}

TypeId
BaseTopology::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::eecityu::BaseTopology")
    .SetParent<Object> ()
    .AddAttribute ("PajekArcType", 
                   "Set the type to use as the arc value when outputing a Pajek topology file: (NPackets, Delay, QueueDepth)",
                   EnumValue (BaseTopology::NPackets),
                   MakeEnumAccessor (&BaseTopology::m_pajekArcType),
                   MakeEnumChecker (NPackets,   "NPackets",
                                    Delay,      "Delay",
                                    QueueDepth, "QueueDepth"))
    ;

  return tid;
}

TypeId 
BaseTopology::GetInstanceTypeId (void) const
{
  return GetTypeId ();
}


BaseTopology::BaseTopology(void)
: m_pajekArcType ()
{}

BaseTopology::~BaseTopology (void) 
{
  NS_ASSERT_MSG (false, "not yet implemented");
}

uint32_t 
BaseTopology::ReadBriteNs (string filename)
{
  NS_LOG_FUNCTION (filename);

  typedef map<uint32_t, Ptr<Node> > NodeMap_t;
  typedef pair<uint32_t, uint32_t> Arc_t;
  typedef map<Arc_t, Ptr<PointToPointNetDevice> > ArcMap_t;
  // to store all nodes specified in the topology data
  // key:   the node ID
  // value: the actual node object
  NodeMap_t nodesMap;
  // to store all arcs and point-to-point channel specified in the topology data
  // first_value: an arc specified with an arc-tail node and an arc-head node
  // second_value: the point-to-point net device installed on the arc-tail node
  ArcMap_t arcMap;
  // open the topology data into an input file stream
  ifstream tFile (filename.c_str ());
  // create a helper to create point-to-point between
  PointToPointHelper ppHelper;
  // start reading line-by-line where the definition of link/arc is found.
  // Also, read the link/arc attributes defined on the line
  //
  // Walk through all lines defining the link/arc
  while (true)
  {
    // Jump to the line first where the link/arc is defined
    string linkArcLine = JumpToNextNsLine (tFile);
    // check if the end of file is reached
    if (linkArcLine.empty ())
    {
      NS_LOG_LOGIC ("Reaches the end of the BRITE-NS topology file");
      NS_ASSERT_MSG (tFile.eof (), "The BRITE-NS toopology file does not reach its end-of-file");
      break;
    }
    // Tokenize this line so that each attribute can be read one-by-one
    typedef vector<string> Vec_t;
    Vec_t attributesStrVec;
    CTokenizer<CIsFromString>::Tokenize (attributesStrVec, linkArcLine, CIsFromString (" \n\t\r()"));
    NS_LOG_LOGIC (PrintStringVector (attributesStrVec));
    // a sample of the tokenized attributes
    // BaseTopology:ReadBriteNs(): Link/arc attributes: <$ns><duplex-link><$n><0><$n><863><10.0Mb><0.5706383840968394ms><$qtype>
    // Thus, attributes are found in the following position
    // attributesStrVec[1]: simplex-link or duplex-link
    // attributesStrVec[3]: arc-tail node-ID
    // attributesStrVec[5]: arc-head node-ID
    // attributesStrVec[6]: arc bandwidth
    // attributesStrVec[7]: arc propagation delay
    //
    // First of all, check for the file syntax
    NS_ASSERT_MSG (attributesStrVec.at (0).compare ("$ns") == 0, 
      "Syntax error in BRITE-NS topology file! \"$ns\" expected before link-type definition");
    NS_ASSERT_MSG (attributesStrVec.at (2).compare ("$n") == 0,
      "Syntax error in BRITE-NS topology file! \"$n\" expected before the first node definition");
    NS_ASSERT_MSG (attributesStrVec.at (4).compare ("$n") == 0,
      "Syntax error in BRITE-NS topology file! \"$n\" expected before the second node definition");
    NS_ASSERT_MSG (attributesStrVec.at (8).compare ("$qtype") == 0,
      "Syntax error in BRITE-NS topology file! \"$qtype\" expected at the end of a line");
    // basic syntax check passed
    //
    // read link type
    //
    // Either one of the following two flags will be true according to the link type defined in the BRITE-NS topology data
    bool isSimplex = false;
    bool isDuplex  = false;
    NS_LOG_LOGIC ("link type = " << attributesStrVec.at (1));
    if (attributesStrVec.at (1).compare ("simplex-link") == 0)
    {
      isSimplex = true;      
      NS_ASSERT_MSG (0, "Currently simplex-link is not supported!");
    }
    else if (attributesStrVec.at (1).compare ("duplex-link") == 0)
    {
      isDuplex = true;
    }
    else
      NS_ASSERT_MSG (0, "unsupported link type! Only \"simplex-link\" or \"duplex-link\" are supported.");
    //
    // read arc-tail node-ID.
    //
    uint32_t tailNodeId;
    istringstream tailNodeSs (attributesStrVec.at (3));
    tailNodeSs >> tailNodeId;    
    NS_LOG_LOGIC ("Arc tail node = " << tailNodeId);
    //
    // read arc-head node-ID
    //
    istringstream headNodeSs (attributesStrVec.at (5));
    uint32_t headNodeId;
    headNodeSs >> headNodeId;
    NS_LOG_LOGIC ("Arc head node = " << headNodeId);
    //
    // read arc bandwidth
    //
    DataRate dataRate (attributesStrVec.at (6) += "/s");
    NS_LOG_LOGIC ("Line rate = " << dataRate);
    //
    // read propagation delay
    //
    Time delay (attributesStrVec[7]);
    NS_LOG_LOGIC ("line delay = " << delay);

    // get the arc between the two nodes
    Ptr<Node> tailNode = nodesMap [tailNodeId];
    Ptr<Node> headNode = nodesMap [headNodeId]; 
    // Create the nodes if they are not found in this topology object
    NodeContainer newNodes;
    if (tailNode == 0) 
    {
      tailNode = CreateObject<Node> ();
      nodesMap [tailNodeId] = tailNode;
      newNodes.Add (tailNode);
      NS_LOG_LOGIC ("Created new node " << tailNodeId);
    }
    if (headNode == 0)
    {
      headNode = CreateObject<Node> ();
      nodesMap [headNodeId] = headNode;
      newNodes.Add (headNode);
      NS_LOG_LOGIC ("Created new node " << headNodeId);
    }
    // Set attributes of the two net devices on a point-to-point channel. 
    // As the two net devices cannot be set with different attributes, 
    // currently simplex-link is not supported
    ppHelper.SetChannelAttribute ("Delay", TimeValue (delay));
    if (isDuplex)
      ppHelper.SetDeviceAttribute ("DataRate", DataRateValue (dataRate));
    else
      NS_ASSERT_MSG (0, "Do not support simpelx-link as two net devices "
        << "are not allowed to have different attributes set");

    // create the link between the two nodes and get the two new net devices
    NetDeviceContainer newDevs = ppHelper.Install (tailNode, headNode);
    // add two arcs into the topology, each is the same link but with reverse direction
    if (isDuplex)
    {
      // create the link by adding the same arc twice but direction reversed, while 
      // at the same time do not allow creating duplicated link
      //
      // The arc (x->y) is added into the 'arcMap' in such a way that the net device
      // installed on the node-x can be referenced
      //
      // add the arc (tail->head) and check if this arc exists already
      bool isNew = arcMap.insert (ArcMap_t::value_type (
        Arc_t (tailNodeId, headNodeId), 
        DynamicCast<PointToPointNetDevice> (newDevs.Get (0))
        )).second;
      NS_ASSERT_MSG (isNew, "Do not allow creating a duplicated link (" 
        << tailNodeId << ", " 
        << headNodeId << ")");
      NS_LOG_LOGIC ("Created a new arc (" << tailNodeId << ", " << headNodeId << ")");
      // add the arc (head->tail) and check if this arc exists already
      isNew = arcMap.insert (ArcMap_t::value_type (
        Arc_t (headNodeId, tailNodeId), 
        DynamicCast<PointToPointNetDevice> (newDevs.Get (1))
        )).second;
      NS_ASSERT_MSG (isNew, "Do not allow creating a duplicated link (" 
        << headNodeId << ", " 
        << tailNodeId << ")");
      NS_LOG_LOGIC ("Created a new arc (" << headNodeId << ", " << tailNodeId << ")");
    }
    else
      NS_ASSERT_MSG (0, "Cannot add only one arc into the topology. "
        << "A bi-directional link is assumed");
    // add the new nodes and new net-devices into the topology
    m_nodes.Add (newNodes);
    m_devices.Add (newDevs);
  } // end of walking through all links

  // release the file after use
  tFile.close ();

  NS_ASSERT_MSG (nodesMap.size () == m_nodes.GetN (), "Incorrect number of nodes stored in the topology object");
  NS_ASSERT_MSG (arcMap.size () == m_devices.GetN (), "Incorrect number of net devices stored in the topology object");
  NS_LOG_INFO ("Number of nodes created = " 
    << m_nodes.GetN () << ", number of net devices created = " 
    << m_devices.GetN ());

  return arcMap.size ();
}

string 
BaseTopology::PrintPajek () const
{
  NS_LOG_FUNCTION_NOARGS ();

  // the width in a line of string for writing node id
  const static uint32_t NOD_ID_WIDTH = 5;

  typedef map< Ptr<Node>, uint32_t > NodePajekMap_t;
  typedef set< Ptr<Channel> > ChannelSet_t;
  // remember the mapping between a node and the Pajet Id. This mapping
  // is created when printing out nodes and is re-used when printing out arcs
  NodePajekMap_t nodePajekMap;
  // remember which channel is already printed
  ChannelSet_t printedChannelSet;
  // store the output string definiting this topology
  ostringstream os;
  // get the number of nodes in this topology
  uint32_t numNodes = m_nodes.GetN ();
  NS_LOG_LOGIC ("Number of nodes to be printed = " << numNodes);
  // get the number of arcs in this topology. Note that a point-to-point link
  // is counted as two arcs, as it is equal to the number of net devices and
  // counting the number of net devices is very easy.
  uint32_t numArcs = m_devices.GetN ();
  NS_LOG_LOGIC ("Number of arcs to be printed = " << numArcs);
  
  // start printing nodes
  //
  // print out the first line of nodes definition
  os << "*Vertices " << numNodes << endl;
  // walk through all nodes and print them out
  for (uint32_t i = 0; i < numNodes; i++)
  {
    // get a node and print it out
    Ptr<Node> n = m_nodes.Get (i);
    // give this node a Pajek id
    uint32_t id = i+1;
    ostringstream GraphNode;
    GraphNode << "\"id-" << n->GetId () << "\"";
    os << setw (4)  << setiosflags (ios::right) << id << " " << resetiosflags (ios::right)
       << setw (41) << setiosflags (ios::left)  << GraphNode.str ()
       << GetNodePosition (n)      << endl;
    // save the mapping between the node and its Pajek Id
    nodePajekMap [n] = id;
  }

  // start printing arcs
  //
  // print out the first line of arcs definition
  os << "*Arcs" << endl;
  // count the number of arcs printed at the end
  uint32_t numArcsPrinted = 0;
  // walk through all net devices and print them out
  for (uint32_t i = 0; i < numArcs; i++)
  {
    // get the net device and print its point-to-point 
    Ptr<NetDevice> printDev = m_devices.Get (i);
    // get a point-to-point channel, check if the channel is already printed, 
    // and get the two net devices finally
    Ptr<Channel> channel = printDev->GetChannel ();
    // get the two net devices attached on this point-to-point channel
    Ptr<NetDevice> dev0 = channel->GetDevice (0);
    Ptr<NetDevice> dev1;
    // make sure the arc always starts from the 'printDev'
    if (printDev == dev0)
      dev1 = channel->GetDevice (1);
    else
    {
      dev1 = dev0;
      dev0 = channel->GetDevice (1);
    }      

    // get the arc value
    string arcValue = GetArcValue (dev1);

    // get the two corresponding nodes of the two net devices
    Ptr<Node> n0 = dev0->GetNode ();
    Ptr<Node> n1 = dev1->GetNode ();
    // get the two corresponding Pajek ID of the two nodes
    uint32_t id0 = nodePajekMap.find (n0)->second;
    uint32_t id1 = nodePajekMap.find (n1)->second;
    NS_LOG_LOGIC ("Printing arc (" << id0 << ", " << id1 << ")");
    os << setw (NOD_ID_WIDTH) << setiosflags (ios::left) << id1
       << setw (NOD_ID_WIDTH) << setiosflags (ios::left) << id0
       << setw (NOD_ID_WIDTH) << setiosflags (ios::left) << arcValue;   
    // print a next-line character if there are still arcs to be printed
    if (i < numArcs) os << endl;
    // count the number of arcs printed. Each edge is counted as 2 arcs
    numArcsPrinted ++;
  }
  NS_ASSERT_MSG (numArcsPrinted == numArcs, "There are " 
    << numArcs - numArcsPrinted 
    << " arcs missing when printing the topology to a file!");

  return os.str ();
}

string 
BaseTopology::JumpToNextNsLine (istream& is)
{
  NS_LOG_FUNCTION ("istream&");

  // a buffer to store one line of the input file stream
  string lineBuf;
  // the flag turns true if it is found the keyword 'simplex-link' in a line of the input stream
  bool foundSimplex = false;
  // the flag turns true if it is found the keyword 'duplex-link' in a line of the input stream
  bool foundDuplex = false;
  uint32_t lineSkipped = 0;
  // read the file line-by-line until the keyword 'duplex-link' or 'simplex-link' are found
  //
  // walk though the input stream line-by-line until one of the two keyword is found
  do 
  {
    // there is one unnecessary increment applied to lineSkipped. It is removed after the loop is exited
    lineSkipped++;
    // check if the input stream reaches its end
    if (is.eof ())
    {
      lineBuf.clear ();
      NS_ASSERT_MSG (lineBuf.empty (), "Unable to clear the content of a string");
      break;
    }
    lineBuf.clear ();
    getline (is, lineBuf);
    foundSimplex = lineBuf.find ("simplex-link") != string::npos;
    foundDuplex  = lineBuf.find ("duplex-link")  != string::npos;
  } while (!foundSimplex && !foundDuplex);
  lineSkipped--;

  NS_LOG_LOGIC ("Jump " << lineSkipped << " lines to here: " << lineBuf);

  return lineBuf;
}

uint32_t 
BaseTopology::GetNNodes () const
{
  NS_LOG_FUNCTION_NOARGS ();
  return m_nodes.GetN ();
}

uint32_t 
BaseTopology::GetNLinks () const
{
  NS_LOG_FUNCTION_NOARGS ();
  uint32_t numDevs = m_devices.GetN ();
  NS_ASSERT_MSG (numDevs % 2 == 0, "Number of net devices cannot be an odd number as net devices have to exist in pairs!");
  return numDevs / 2;
}

NodeContainer& 
BaseTopology::GetNodeContainer (void)
{
  return m_nodes;
}

const NodeContainer& 
BaseTopology::GetNodeContainer (void) const
{
  return m_nodes;
}

NetDeviceContainer& 
BaseTopology::GetNetDeviceContainer (void)
{
  return m_devices;
}

const NetDeviceContainer& 
BaseTopology::GetNetDeviceContainer (void) const
{
  return m_devices;
}

string 
BaseTopology::GetArcValue (Ptr<NetDevice> dev) const
{
  NS_LOG_FUNCTION (dev);

  NS_LOG_LOGIC ("Pajek ArcType = " << m_pajekArcType);
  switch (m_pajekArcType)
  {
  case NPackets:
    return GetArcNPackets (dev);
  case Delay:
    return GetArcDelay (dev);
  case QueueDepth:
    return GetArcQueueDepth (dev);
  default:
    NS_ASSERT_MSG (0, "Unknown ArcType when outputing Pajek topology file.");
    return "";
  };
}

string 
BaseTopology::GetArcNPackets (Ptr<NetDevice> dev) const
{
  NS_LOG_FUNCTION (dev);

  // get the queue of this net-device and set the number of bytes traversed
  PointerValue queuePV;
  dev->GetAttribute ("TxQueue", queuePV);
  Ptr<Queue> queue = queuePV.GetObject ()->GetObject<Queue> ();
  uint32_t arcValue = queue->GetTotalReceivedPackets ();
  // return 1 if the arcValue is 0; return the arcValue itself otherwise
  ostringstream os;
  if (arcValue == 0)  os << -1;
  else                os << arcValue;
  return os.str ();
}

string 
BaseTopology::GetArcDelay (Ptr<NetDevice> dev) const
{
  NS_LOG_FUNCTION (dev);
  // get the delay attribute associated with the point-to-point-channel
  TimeValue delayTV;
  dev->GetChannel ()->GetAttribute ("Delay", delayTV);
  Time delay = delayTV.Get ();
  // write the delay into a string and return it
  ostringstream os;
  os << delay.GetSeconds ();
  return os.str ();
}

string 
BaseTopology::GetArcQueueDepth (Ptr<NetDevice> dev) const
{
  NS_LOG_FUNCTION (dev);
  // obtain the queue depth
  UintegerValue queueDepthUV;
  PointerValue queuePV;
  dev->GetAttribute ("TxQueue", queuePV);
  queuePV.Get<Queue> ()->GetAttribute ("MaxPackets", queueDepthUV);
  // write the queue depth into a string and return it
  ostringstream os;
  os << queueDepthUV.Get ();
  return os.str ();
}

string 
BaseTopology::GetNodePosition (Ptr<Node> n) const
{
  NS_LOG_FUNCTION (n);
  return "";
}

} // namespace eecityu
} // namespace ns3